
function CameraSystem() {}

CameraSystem.prototype = {
    // 初期化
    initialize: function(w,h,fovy,aspect,znear,zfar) {
        // 指定しなかった場合にはデフォルトの値を使う
        var default_val = {
            "fovy"   : 45.0,
            "aspect" : 1.0,
            "znear"  : 0.1,
            "zfar"   : 100,
        };
        default_val.width = w;
        default_val.height = h;
        if (fovy){
            default_val.fovy = fovy;
        }
        if (aspect){
            default_val.aspect = aspect;
        }
        if (znear){
            default_val.znear = znear;
        }
        if (zfar){
            default_val.zfar = zfar;
        }
        this.screen = default_val;
        return this;
    },
    // カメラ用の行列の作成
    toMatrix: function() {
        var zvec = this.eye_rvect.toUnitVector(); 
        var up = $V([0,1,0]);
        var xvec = up.cross(zvec).toUnitVector();
        var yvec = zvec.cross(xvec).toUnitVector();

        var m = $M([[xvec.e(1),xvec.e(2),xvec.e(3),0],
                    [yvec.e(1),yvec.e(2),yvec.e(3),0],
                    [zvec.e(1),zvec.e(2),zvec.e(3),0],
                    [0, 0, 0, 1]]);
        var pos = this.eye_pos;
        var t = $M([[1, 0, 0, -pos.e(1)],
                    [0, 1, 0, -pos.e(2)],
                    [0, 0, 1, -pos.e(3)],
                    [0, 0, 0, 1]]);
        return m.x(t);
    },
    // カメラ用の行列の作成(3x3)
    toMatrix3x3: function() {
        var zvec = this.eye_rvect.toUnitVector(); 
        var up = $V([0,1,0]);
        var xvec = up.cross(zvec).toUnitVector();
        var yvec = zvec.cross(xvec).toUnitVector();

        return $M([[xvec.e(1),xvec.e(2),xvec.e(3)],
                    [yvec.e(1),yvec.e(2),yvec.e(3)],
                    [zvec.e(1),zvec.e(2),zvec.e(3)]]);
    },
    // WebGLへの描画前の設定とカメラ座標への変換行列の取得
    exec: function() {
        var screen = this.screen;
        gl.viewport(0, 0, screen.width, screen.height);
        //perspective(screen.fovy, screen.aspect, screen.znear, screen.zfar);
        this.pMatrix = makePerspective(screen.fovy, screen.aspect, screen.znear, screen.zfar);
        this.pMatrixFlatten = new Float32Array(this.pMatrix.flatten());
        // カメラのアニメーションが登録されていれば実行する
        if (this.animation_que.length){
            // 戻り値が0でなければキューから削除する
            if (this.animation_que[0]()){
                this.animation_que.shift();
            }
        }
        // カメラの座標系へ変換するための行列の作成
        return this.toMatrix();
    },
    // カメラ座標の取得
    getPos: function() {
        return this.eye_pos.dup();
    },
    // カメラの座標の設定
    setPos: function(px, py, pz) {
        this.eye_pos = $V([px, py, pz]);
    },
    // x軸でのカメラの回転
    pitch: function(angle) {
        //var matrix = this.toMatrix(up).x(Matrix.RotationX(angle).ensure4x4());
        var matrix = Matrix.RotationX(angle).ensure4x4().x(this.toMatrix());
        this.eye_rvect.elements[0] = matrix.elements[2][0];
        this.eye_rvect.elements[1] = matrix.elements[2][1];
        this.eye_rvect.elements[2] = matrix.elements[2][2];
    },
    // y軸でのカメラの回転
    yaw: function(angle) {
        /*var matrix = this.toMatrix().x(Matrix.RotationY(angle).ensure4x4());
        //var matrix = Matrix.RotationY(angle).ensure4x4().x(this.toMatrix());
        this.eye_rvect.elements[0] = matrix.elements[2][0];
        this.eye_rvect.elements[1] = matrix.elements[2][1];
        this.eye_rvect.elements[2] = matrix.elements[2][2];*/
        this.eye_rvect = Matrix.RotationY(angle).x(this.eye_rvect);
    },
    // lookat
    lookAt: function(ox, oy, oz) {
        var epos = this.eye_pos;
        this.eye_rvect = $V([epos.e(1)-ox, epos.e(2)-oy, epos.e(3)-oz]).toUnitVector();
    },
    // カメラの移動
    move: function(mx, my, mz) {
        var matrix = this.toMatrix3x3();
        var v = matrix.transpose().x($V([mx, my, mz]));
        this.eye_pos = this.eye_pos.add(v);
    },
    // hermite補完アニメーションの登録
    // sposとsvecが指定されない場合には現在のカメラからスタート      
    addHermiteAnimation: function(time,spos, srvec, endpos, endrvec) { 
        this.animation_que.push(hermiteAnime(this, time, spos, srvec, endpos, endrvec));
    },
    // debug  カメラの座標と視線の逆ベクトルの文字列への変換
    getCameraString: function() {
        return "camera position:" + "[" + this.eye_pos.e(1).toFixed(4) + "," + 
                                          this.eye_pos.e(2).toFixed(4) + "," + 
                                          this.eye_pos.e(3).toFixed(4) + "]<br>" +
                "camera rvect   :" + "[" + this.eye_rvect.e(1).toFixed(4) + "," + 
                                           this.eye_rvect.e(2).toFixed(4) + "," + 
                                           this.eye_rvect.e(3).toFixed(4) + "]<br>";
        },
    // debug　カメラ行列を文字列へ変換
    getMatrixString: function() {
        return mht(this.toMatrix().flatten());
    },
    // debug　カメラ行列の逆行列を文字列へ変換
    getInvMatrixString: function() {
        return mht(this.toMatrix().inverse().flatten());
    },
};

// カメラオブジェクトの生成
CameraSystem.create = function() {
    var camera = new CameraSystem();
    //camera.matrix = Matrix.I(4);
    camera.eye_pos = $V([0, 0, 0]);
    camera.eye_rvect = $V([0, 0, 1]);
    camera.animation_que = []; // カメラアニメーションのキュー
    return camera;
}

// Hermiteカメラアニメーション関数の生成
var hermiteAnime = function(camera, time, spos, srvec, epos, ervec){
    var elapse;
    var lastTime = 0; 
    return function(){
        var t;
        var rt;
        var x, y, z;
        var ret = 0;
        if (lastTime == 0){
            elapse = 0; 
            lastTime = new Date().getTime();
            spos = spos || camera.eye_pos.elements;
            srvec = srvec || camera.eye_rvect.elements;
        } else {
            var curTime = new Date().getTime();
            elapse = curTime - lastTime;
            curTime = lastTime;
        }
        t = elapse / time;
        if (t >= 1){
            t = 1;
            ret = 1;
        }
        camera.eye_pos = $V(hermite(t, spos, srvec, epos, ervec));
        rt = 1 - t;
        x = srvec[0]*rt + ervec[0]*t;
        y = srvec[1]*rt + ervec[1]*t;
        z = srvec[2]*rt + ervec[2]*t;
        camera.eye_rvect = $V([x, y, z]).toUnitVector();
        
        return ret;
    };
}

